import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { DiscordSnowflake } from "@sapphire/snowflake";
import { plainToInstance } from "class-transformer";
import { DataSource, FindOptionsSelect, In, Repository } from "typeorm";
import { ResultMsg } from "@/common/enum/result-msg.enum";
import Result from "@/common/utils/result.util";
import { SysMenu } from "@/database/entity/SysMenu";
import { SysPermission } from "@/database/entity/SysPermission";
import { PermService } from "../permission/perm.service";
import { CreateMenuDto } from "./dto/create-menu.dto";
import { UpdateMenuDto } from "./dto/update-menu.dto";
import { MenuDetailVo } from "./vo/menu-detail.vo";
import { MenuParentVo } from "./vo/menu-parent.vo";
import { MenuTreeVo } from "./vo/menu-tree.vo";

const detailSelectFields: FindOptionsSelect<SysMenu> = {
    id: true,
    parentId: true,
    name: true,
    path: true,
    component: true,
    meta: true,
    redirect: true,
};

@Injectable()
export class MenuService {
    constructor(
        @InjectRepository(SysMenu) private menuRepository: Repository<SysMenu>,
        private dataSource: DataSource,
        private permService: PermService,
    ) {}

    /** 新增系统菜单 */
    async create(menu: CreateMenuDto) {
        const id = DiscordSnowflake.generate().toString();
        const record = this.menuRepository.create({ ...menu, id });
        await this.menuRepository.save(record);
        return Result.success(id);
    }

    /** 删除系统菜单 */
    async remove(id: string) {
        await this.dataSource.transaction(async (manager) => {
            // 查询所有子节点
            const rows = await manager.query(
                `
                WITH RECURSIVE sub AS (
                  SELECT id
                  FROM sys_menu
                  WHERE id = $1 AND deleted_at IS NULL

                  UNION ALL

                  SELECT m.id
                  FROM sys_menu m
                  INNER JOIN sub s ON m.parent_id = s.id
                  WHERE m.deleted_at IS NULL
                )
                SELECT id FROM sub;
                `,
                [id],
            );

            if (rows.length === 0) return;
            const ids = rows.map((r: any) => r.id);
            // 软删除 sys_menu
            await manager.update(SysMenu, { id: In(ids) }, { deletedAt: new Date() });
            // 软删除 sys_permission
            await manager.update(SysPermission, { menuId: In(ids) }, { deletedAt: new Date() });
        });
        return Result.success(null, ResultMsg.DELETE_SUCCESS);
    }

    /** 编辑系统菜单 */
    async update(menu: UpdateMenuDto) {
        const exist = await this.menuRepository.findOneBy({ id: menu.id });
        if (!exist) {
            return Result.error(ResultMsg.DATA_NOT_FOUND);
        }
        await this.menuRepository.save(menu);
        return Result.success(null, ResultMsg.HANDLE_SUCCESS);
    }

    /** 获取菜单树 */
    async listTree(): Promise<Result<MenuTreeVo[]>> {
        const permissions = await this.permService.listMenus(); // 获取按钮权限
        const query = await this.menuRepository.find({
            select: detailSelectFields,
            order: { createdAt: "ASC" },
        });
        const menus = formatMenusTree([...query, ...permissions]);
        return Result.success(menus);
    }

    /** 根据id查询所有父节点 */
    async listParentById(id: string): Promise<Result<MenuParentVo>> {
        const list = await this.menuRepository.find({
            select: detailSelectFields,
        });
        const map = new Map(list.map((r) => [r.id, r]));
        const getPath = (leafId: string) =>
            ({
                ids: [] as string[],
                nodes: [] as typeof list,
                compute() {
                    let cur = map.get(leafId);
                    while (cur) {
                        this.ids.unshift(cur.id);
                        this.nodes.unshift(cur);
                        cur = cur.parentId ? map.get(cur.parentId) : undefined;
                    }
                    return this;
                },
            }).compute();
        const { ids, nodes } = getPath(id);
        const listVo = plainToInstance(MenuDetailVo, nodes, { excludeExtraneousValues: true });
        return Result.success({
            ids: ids,
            menus: listVo,
        });
    }

    /** 根据角色获取路由 */
    async listRouteTree(roles: string[]) {
        const query = await this.dataSource
            .createQueryBuilder(SysMenu, "sm")
            .select([
                "sm.id",
                "sm.parentId",
                "sm.name",
                "sm.path",
                "sm.component",
                "sm.meta",
                "sm.redirect",
                "sm.createdAt",
            ])
            .innerJoin("sys_role_menu", "srm", "srm.menu_id = sm.id")
            .innerJoin("sys_role", "sr", "sr.id = srm.role_id")
            .where("sr.code = ANY(:roles)", { roles })
            .orderBy("sm.created_at", "ASC")
            .distinct(true)
            .getMany();
        return formatMenusTree(query);
    }
}

function formatMenusTree(query: (SysMenu | MenuTreeVo)[]): MenuTreeVo[] {
    /**
     * 踩坑: 使用 getMany() 方法可以自动映射实体字段（在系统中是驼峰命名）
     * 使用 getRawMany() 方法返回的是数据库原生字段（在系统中是下划线命名）
     */
    const menus = plainToInstance(MenuTreeVo, [...query], { excludeExtraneousValues: true });
    // 前端设置的排序最大值是 9999, 所以这里用 10000 作为默认值, 保证没有设置排序值的排在最后
    menus.sort((a, b) => ((a.meta?.rank ?? 10000) > (b.meta?.rank ?? 10000) ? 1 : -1));
    menus.forEach((menu) => {
        // 由于上面已经排序好了，所以这里直接过滤返回的子节点是有序的
        const children = menus.filter((child) => child.parentId === menu.id);
        menu.children = children.length ? children : null;
    });
    return menus.filter((menu) => !menu.parentId);
}
